package com.ibm.cps.graphite;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ibm.cps.message.MessageFields;
import com.ibm.util.ErrorCode;
import com.ibm.util.JSONUtility;
import com.ibm.util.exception.CPSException;

public class GraphiteDataMessage {
	private JsonNode node;
	private Map<String, List<GraphiteMessage>> targetsMap;
	private ObjectNode jointMessage;
	private static final String TARGET = "target";
	private static final String DATAPOINTS = "datapoints";
	private String[] targetArray;
	private static final long JOINT_TIMESTAMP_THRESHOLD = 100;

	/**
	 * [{"target":"collectdlocalhostcollectd.battery-0.charge","datapoints":[[3.43,1438757600]]}]
	 * @param node
	 * @throws CPSException 
	 */
	public GraphiteDataMessage(JsonNode node, String... targetArray)
			throws CPSException {
		this.node = node;
		this.targetArray = targetArray;
		targetsMap = new HashMap<String, List<GraphiteMessage>>();
		ArrayNode array = JSONUtility.getArrayFromNode(node);
		if (array != null && array.size() >= 1) {
			for (int i = 0; i < array.size(); i++) {
				ObjectNode aTarget = (ObjectNode) array.get(i);
				String target = aTarget.get(TARGET).asText();
				targetsMap.put(target, new ArrayList<GraphiteMessage>());
				if (aTarget.get(DATAPOINTS).isArray()) {
					Iterator<JsonNode> values = aTarget.get(DATAPOINTS)
							.iterator();

					// each target has a series of data points
					while (values.hasNext()) {
						JsonNode message = values.next();
						Iterator<JsonNode> fields = message.iterator();
						JsonNode _tmpnode = fields.next();
						if (_tmpnode instanceof NullNode ) {
							continue;
						}
						double value = _tmpnode.asDouble();
						long timestamp = fields.next().asLong();
						targetsMap.get(target).add(
								new GraphiteMessage(1, value, timestamp));
					}
				}
			}

			jointMessage = joinAccordingTimestamp(getFirstMessageFromTarget(targetsMap));
		}
	}

	public Map<String, GraphiteMessage> getFirstMessageFromTarget(
			Map<String, List<GraphiteMessage>> target2Messages)
			throws CPSException {
		// validate if datapoint is null, and if there exists more than one datapoint,get the first one and trash other.
		if (target2Messages == null) {
			return null;
		}
		Map<String, GraphiteMessage> target2Message = new HashMap<String, GraphiteMessage>();
		for (Map.Entry<String, List<GraphiteMessage>> entry : target2Messages
				.entrySet()) {
			if (entry.getValue() != null && entry.getValue().size() >= 1) {
				target2Message.put(entry.getKey(), entry.getValue().get(0));
			} else {
				throw new CPSException(ErrorCode.INVALID_INPUT_ERROR,
						"Value for target " + entry.getKey()
								+ " should not be null.");
			}
		}
		return target2Message;
	}

	/**
	 * joint a list of messages according to timestamp
	 * @param target2Message
	 * @return
	 * @throws CPSException
	 */
	public ObjectNode joinAccordingTimestamp(
			Map<String, GraphiteMessage> target2Message) throws CPSException {
		if (targetArray == null) {
			return null;
		}
		boolean isFirst = true;
		long previousTimestamp = 0, currentTimestamp = 0;

		ObjectNode node = JSONUtility.newObjectNode();
		node.put(MessageFields.TSKEY, 1);
		for (Map.Entry<String, GraphiteMessage> entry : target2Message
				.entrySet()) {
			currentTimestamp = entry.getValue().getTimestamp();
			if (isFirst) {
				isFirst = false;
				node.put(MessageFields.TIMESTAMP, currentTimestamp);
				previousTimestamp = currentTimestamp;
			} else {
				if (Math.abs(previousTimestamp - currentTimestamp) > JOINT_TIMESTAMP_THRESHOLD) {
					throw new CPSException(ErrorCode.INVALID_INPUT_ERROR,
							"Timestamps are diffenert, one is "
									+ currentTimestamp + ", the other is "
									+ previousTimestamp
									+ ", so they can't be joint.");
				}
			}
			node.put(entry.getKey().replaceAll("[.-]", ""), entry.getValue()
					.getValue());
		}
		return node;
	}

	public Set<String> getTargets() {
		return targetsMap.keySet();
	}

	public List<GraphiteMessage> getTargetMessage(String target) {
		return targetsMap.get(target);
	}

	public ObjectNode getJointMessage() {
		return jointMessage;
	}

	public static void main(String[] args) {
		String test = "localhost.cpu.8.cpu.idle.value";
		System.out.println(test.replaceAll("[.]", ""));
	}
}

class GraphiteMessage {
	private double value;
	private long timestamp;
	private int tsKey;

	public GraphiteMessage(int tsKey, double value, long timestamp) {
		this.tsKey = tsKey;
		this.value = value;
		this.timestamp = timestamp;
	}

	public double getValue() {
		return value;
	}

	public long getTimestamp() {
		return timestamp;
	}

	public String toString() {
		ObjectNode node = JSONUtility.newObjectNode();
		node.put("k", tsKey);
		node.put("t", timestamp);
		node.put("v", value);
		return node.toString();
	}

}
